/**
 * Chemistry Library
 */

/* Chemistry Namespace */
c$ = {};

c$.Settings = {
	InteractionDelay : 200,
	DefaultAnimationDurationMillis : 1000,
	AnimationFPS : 30
};

c$.bootstrap = new function c$bootstrap() {
	this.HTMLElement = document.createElement("span");
	this.StyleObject = this.HTMLElement.style;
}();

c$.isObject = function c$isObject(object) {
	return object != null && typeof object == "object";
};
c$.isBuiltBy = function c$isBuiltBy(object, ctor) {
	return c$.isObject(object) && object.constructor == ctor;
};

/**
 * @param {Object}
 *            object
 * @param {String}
 *            methodName
 * @returns {Boolean}
 */
c$.hasMethod = function() {
	if (typeof alert == "function") {
		return function c$hasMethod(object, methodName) {
			if (c$.isObject(object)) {
				var typeName = typeof object[methodName];
				return typeName == "function";
			} else {
				return false;
			}
		};
	} else {
		return function c$hasMethod$ie(object, methodName) {
			if (c$.isObject(object)) {
				var typeName = typeof object[methodName];
				return typeName == "function" || typeName == "object";
			} else {
				return false;
			}
		};
	}
}();

c$.delegate = function c$delegate(ctor, memberName, methodName) {
	ctor.prototype[methodName] = function() {
		this[memberName][methodName].apply(this[memberName], arguments);
	};
};

c$.DelayedCall = function c$DelayedCall(delay, callFn) {
	this.delayMillis = delay;
	this.callFn = callFn;
};
c$.DelayedCall.prototype.setTimeout = function c$DelayedCall$setTimeout(args) {
	this.timeout = setTimeout(function(_this, _args) {
		return function() {
			_this.callFn.apply(_this, _args);
		};
	}(this, args), this.delayMillis);
};
c$.DelayedCall.prototype.delay = function c$DelayedCall$delay() {
	if (this.timeout) {
		clearTimeout(this.timeout);
		delete this.timeout;
	}
	this.setTimeout(arguments);
};
c$.Array = {};
c$.Array.indexOf = function() {
	if (typeof Array.prototype.indexOf != "undefined") {
		return function c$Array$indexOf$stub(_array, _searchItem) {
			return _array.indexOf(_searchItem);
		};
	} else {
		return function c$Array$indexOf(_array, _searchItem) {
			for ( var i = 0; i < _array.length; i++) {
				if (_array[i] === _searchItem) {
					return i;
				}
			}
			return -1;
		};
	}
}();
c$.Array.remove = function(_array, elem) {
	var idx = c$.Array.indexOf(_array, elem);
	if (idx > -1) {
		_array.splice(idx, 1);
	}
};
c$.Array.insertUnique = function(_array, elem) {
	if (c$.Array.indexOf(_array, elem) == -1) {
		_array.push(elem);
	}
};

/* Event Handling */
/**
 * @constructor
 * @param {String}
 *            name
 * @param {Element}
 *            target
 * @param {Boolean}
 *            useCapture
 */
c$.EventBinding = function c$EventBinding(name, target, useCapture) {
	this.name = name;
	this.target = target;
	this.useCapture = useCapture;
	this.eventCallback = function(_this) {
		return function(event) {
			return _this.handleEvent(event);
		};
	}(this);
	this.bound = false;
};
c$.EventBinding.prototype.bind = function() {
	if (c$.hasMethod(c$.bootstrap.HTMLElement, "addEventListener")) {
		return function c$EventBinding$bind$ael() {
			if (!this.bound) {
				this.target.addEventListener(this.name, this, this.useCapture);
				this.bound = true;
			}
			return this;
		};
	} else if (c$.hasMethod(c$.bootstrap.HTMLElement, "attachEvent")) {
		return function c$EventBinding$bind$ae() {
			if (!this.bound) {
				if (c$.hasMethod(this.target, "addEventListener")) {
					this.target.addEventListener(this.name, this,
							this.useCapture);
				} else {
					this.target.attachEvent("on" + this.name,
							this.eventCallback);
				}
				this.bound = true;
			}
			return this;
		};
	} else {
		return function() {
			throw new Error("Your browser does not support event listeners");
		};
	}
}();
c$.EventBinding.prototype.unbind = function() {
	if (c$.hasMethod(c$.bootstrap.HTMLElement, "removeEventListener")) {
		return function c$EventBinding$unbind$rel() {
			if (this.bound) {
				this.target.removeEventListener(this.name, this,
						this.useCapture);
				this.bound = false;
			}
			return this;
		};
	} else if (c$.hasMethod(c$.bootstrap.HTMLElement, "detachEvent")) {
		return function c$EventBinding$unbind$de() {
			if (this.bound) {
				this.target.detachEvent("on" + this.name, this.eventCallback);
				this.bound = false;
			}
			return this;
		};
	}
}();
c$.EventBinding.prototype.handleEvent = function c$EventBinding$handleEvent(
		event) {
};
c$.EventBinding.prototype.setHandleEventFn = function c$EventBinding$setEventHandler(
		fn) {
	if (c$.hasMethod(fn, "handleEvent")) {
		this.handleEvent = function c$EventBinding$handleEvent$Delegate(event) {
			fn.handleEvent(event);
		};
	} else {
		this.handleEvent = fn;
	}
	return this;
};

c$.EventListenerQueue = function c$EventListenerQueue() {
	this.listeners = [];
};
c$.EventListenerQueue.prototype.addEventListener = function(listener) {
	if (typeof listener == "function") {
		listener.handleEvent = function(_listener) {
			return function handleEvent$self(event) {
				listener(event);
			};
		}(listener);
	}
	c$.Array.insertUnique(this.listeners, listener);
};
c$.EventListenerQueue.prototype.removeEventListener = function(listener) {
	c$.Array.remove(this.listeners, listener);
};
c$.EventListenerQueue.prototype.fireEvent = function c$EventListenerQueue$fireEvent(
		event) {
	for ( var i = 0; i < this.listeners.length; i++) {
		this.listeners[i].handleEvent(event);
	}
};
c$.EventListenerQueue.prototype.isEmpty = function c$EventListenerQueue$isEmpty() {
	return this.listeners.length == 0;
};

c$.EventManager = function c$EventManager() {
	this.listeners = {};
};
c$.EventManager.prototype.addEventListener = function c$EventManager$addEventListener(
		eventName, listener) {
	if (typeof this.listeners[eventName] == "undefined") {
		this.listeners[eventName] = new c$.EventListenerQueue();
	}
	this.listeners[eventName].addEventListener(listener);
};
c$.EventManager.prototype.removeEventListener = function c$EventManager$removeEventListener(
		eventName, listener) {
	var queue = this.listeners[eventName];
	if (typeof queue != "undefined") {
		queue.removeEventListener(listener);
		if (queue.isEmpty()) {
			delete this.listeners[eventName];
		}
	}
};
c$.EventManager.prototype.fireEvent = function c$EventManager$fireEvent(
		eventName, event) {
	var queue = this.listeners[eventName];
	if (typeof queue != "undefined") {
		queue.fireEvent(event);
	}
};

/**
 * 
 * @param {Element}
 *            element
 */
c$.Element = function c$Element(element) {
	this.element = element;
};
c$.Element.prototype.getElement = function c$Element$getElement() {
	return this.element;
};
c$.Element.prototype.appendTo = function c$Element$appendTo(element) {
	element.appendChild(this.getElement());
	return this;
};
c$.Element.prototype.appendChild = function c$Element$appendChild(element) {
	if (c$.isBuiltBy(element, this.constructor)) {
		this.getElement().appendChild(element.getElement());
	} else {
		this.getElement().appendChild(element);
	}
	return this;
};
c$.Element.prototype.addEventBinding = function c$Element$addEventListener(
		name, handler) {
	return new c$.EventBinding(name, this.getElement()).setHandleEventFn(
			handler).bind();
};
c$.Element.prototype.setAttribute = function c$Element$setAttribute(name, value) {
	this.getElement().setAttribute(name, value);
	return this;
};
c$.Element.prototype.getAttribute = function c$Element$setAttribute(name) {
	return this.getElement().getAttribute(name);
};
c$.Element.prototype.setAttributes = function c$Element$setAttributes(
		attributes) {
	for ( var name in attributes) {
		this.setAttribute(name, attributes[name]);
	}
	return this;
};
/**
 * 
 * @param {String}
 *            name
 * @param {String}
 *            value
 */
c$.Element.prototype.setStyle = function() {
	if (c$.hasMethod(c$.bootstrap.StyleObject, "setProperty")) {
		return function c$Element$setStyle(name, value) {
			this.getElement().style.setProperty(name, value, "");
			return this;
		};
	} else {
		return function c$Element$setStyle$ie(name, value) {
			var oldStyle = this.getAttribute("style");
			this.setAttribute("style", (oldStyle != null ? oldStyle + ";" : "")
					+ name + ":" + value);
			return this;
		};
	}
}();
c$.Element.prototype.setStyles = function c$Element$setStyles(styles) {
	for ( var name in styles) {
		this.setStyle(name, styles[name]);
	}
	return this;
};
c$.Element.prototype.clear = function c$Element$clear() {
	this.getElement().innerHTML = "";
	return this;
};
c$.Element.prototype.setText = function c$Element$setText(text) {
	this.clear();
	this.appendChild(document.createTextNode(text));
	return this;
};
c$.Element.create = function c$Element$create(tagName) {
	return new c$.Element(document.createElement(tagName));
};

c$.Window = function c$Window(wObj) {
	this.wObj = wObj;
	this.eventManager = new c$.EventManager();
	var delayFn = function(_this) {
		return function(event) {
			_this.eventManager.fireEvent("delayedResize", event);
		};
	}(this);
	this.delayedResizeCall = new c$.DelayedCall(c$.Settings.InteractionDelay,
			delayFn);
	this.resizeEventBinding = new c$.EventBinding("resize", this.wObj)
			.setHandleEventFn(function(_this) {
				return function(event) {
					_this.delayedResizeCall.delay(event);
				};
			}(this)).bind();
};
c$.delegate(c$.Window, "eventManager", "addEventListener");
c$.delegate(c$.Window, "eventManager", "removeEventLisener");
c$.window = new c$.Window(window);

c$.AnimationQueue = function c$AnimationQueue() {
	this.animations = [];
};
c$.AnimationQueue.FrameEvent = "frame";
c$.AnimationQueue.prototype.start = function c$AnimationQueue$start(animation) {
	if (this.animations.length == 0) {
		this.startLoop();
	}
	c$.Array.insertUnique(this.animations, animation);
};
c$.AnimationQueue.prototype.stop = function c$AnimationQueue$end(animation) {
	c$.Array.remove(this.animations, animation);
	if (this.animations.length == 0) {
		this.endLoop();
	}
};
c$.AnimationQueue.prototype.startLoop = function c$AnimationQueue$startLoop() {
	this.interval = setInterval(function(_this) {
		return function() {
			_this.onInterval();
		};
	}(this), 1000 / c$.Settings.AnimationFPS);
};
c$.AnimationQueue.prototype.endLoop = function c$AnimationQueue$endLoop() {
	clearInterval(this.interval);
	delete this.interval;
};
c$.AnimationQueue.prototype.onInterval = function c$AnimationQueue$onInterval() {
	for ( var i = 0; i < this.animations.length; i++) {
		this.animations[i].onQueue();
	}
};

c$.Animation = function c$Animation(callback) {
	this.callback = callback;
	this.limitsInConstantTimeMillis(0, 1,
			c$.Settings.DefaultAnimationDurationMillis);
	this.setAnimationQueue(c$.Animation.Queue);
	this.reset();
};
c$.Animation.FRAME_PRIORITY = 0;
c$.Animation.SPEED_PRIORITY = 1;
c$.Animation.Queue = new c$.AnimationQueue();
c$.Animation.prototype.setFrameCount = function c$Animation$setFrameCount(
		frames) {
	this.frames = frames;
	this.speed = (this.upper - this.lower) / this.frames;
	this.priority = c$.Animation.FRAME_PRIORITY;
	return this;
};
c$.Animation.prototype.setSpeed = function c$Animation$setSpeed(speed) {
	this.speed = speed;
	this.frames = (this.upper - this.lower) / this.speed;
	this.priority = c$.Animation.SPEED_PRIORITY;
	return this;
};
c$.Animation.prototype.setDurationMillis = function c$Animation$setDurationMillis(
		duration) {
	this.setFrameCount(Math.floor(c$.Settings.AnimationFPS * duration / 1000));
	return this;
};
c$.Animation.prototype.setLimits = function c$Animation$setLimits(lower, upper) {
	this.lower = lower;
	this.upper = upper;
	if (this.priority == c$.Animation.FRAME_PRIORITY) {
		this.setFrameCount(this.frames);
	} else {
		this.setSpeed(this.speed);
	}
	return this;
};
c$.Animation.prototype.limitsInConstantTimeMillis = function c$Animation$lictm(
		lower, upper, time) {
	this.setLimits(lower, upper);
	this.setDurationMillis(time);
	return this;
};
c$.Animation.prototype.limitsWithConstantSpeed = function c$Animation$lwcs(
		lower, upper, speed) {
	this.setLimits(lower, upper);
	this.setSpeed(speed);
	return this;
};
/**
 * 
 * @param {c$.AnimationQueue}
 *            queue
 * @returns
 */
c$.Animation.prototype.setAnimationQueue = function c$Animation$setAnimationQueue(
		queue) {
	if (typeof this.queue != "undefined") {
		delete this.queue;
		this.reset();
	}
	this.queue = queue;
};
c$.Animation.prototype.reset = function c$Animation$reset() {
	this.frame = 0;
	if (this.started) {
		this.die();
	}
};
c$.Animation.prototype.onQueue = function c$Animation$onQueue() {
	this.callback(this.currentValue(), this.lower, this.upper, this.frame++,
			this.frames);
	if (this.frame > this.frames) {
		this.reset();
	};
};
c$.Animation.prototype.currentValue = function c$Animation$currentValue() {
	return this.lower + (this.upper - this.lower) * this.frame / this.frames;
};
c$.Animation.prototype.go = function c$Animation$go() {
	this.queue.start(this);
	this.started = true;
};
c$.Animation.prototype.die = function c$Animation$die() {
	this.queue.stop(this);
	this.started = false;
};

delete c$.bootstrap;